#pragma once
#include "mut.h"
#include "mutresmanager.h"
#include <io.h>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;


#define MD5_VERSION_STRING "MD5Version"
#define MD5_VERSION 10

void MUTModelTest(LPDIRECT3DDEVICE9 pDevice);
void MUTModelRenderTest();


class CMUTMD5Joint{
public:
	char name[32];
	D3DXVECTOR3 t;
	D3DXQUATERNION q;
	CMUTMD5Joint* parent;
};

class CMUTParser{
public:
	char* fileTmp;
	char* curTmp;
	int fsize;
	CMUTParser():fileTmp(0),curTmp(0),fsize(0) {}
	HRESULT OpenHandle(HANDLE hFile);
	void SkipBadChars(){
		while ( *curTmp != 0 && ( *curTmp == ' ' || *curTmp == 9 || *curTmp == 13 || *curTmp == 10 ) ) curTmp++;
	}
	BOOL ExpectTokenString( char* token ){
		SkipComment();
		SkipBadChars();
		int l = strlen( token );
		if ( strncmp( curTmp, token, l ) != 0 ){
			MUTErrorTrace( TString(L"unexpected token ")+TString(token) );
			return FALSE;
		}
		curTmp += l;
		return TRUE;
	}
	BOOL CheckTokenString( char* token ){
		SkipBadChars();
		int l = strlen( token );
		if ( strncmp( curTmp, token, l) == 0 ){
			curTmp += l;
			return TRUE;
		}
		return FALSE;
	}
	void SkipLine(){
		while ( *curTmp != 10 ) curTmp++;
		curTmp++;
	}
	BOOL IsComment(){
		SkipBadChars();
		if ( *curTmp == '/' && ( *( curTmp + 1 ) == '/' || *( curTmp + 1 ) == '*' ) )
			return TRUE;
		return FALSE;
	}
	void SkipComment(){
		SkipBadChars();
		if ( *curTmp == '/' ){
			if ( *( curTmp + 1 ) == '/' )
				SkipLine();
			else if ( *( curTmp + 1 ) == '*' ){
				curTmp += 2;
				while ( *curTmp != '*' || *(curTmp+1) != '/' ) curTmp++;
				curTmp += 2;
			}
		}
	}
	void SkipTable(){
		SkipBadChars();
		if ( *curTmp == '{' ){
			int cnt = 1;
			while ( cnt ){
				curTmp++;
				if ( *curTmp == '{' )
					cnt++;
				else if ( *curTmp == '}' )
					cnt--;
			}
			curTmp++;
		}
	}
	char* FindBreakChar(){
		char* endchar = curTmp;
		while ( *endchar != ' ' && *endchar != 9 && *endchar != 13 ) endchar++;
		return endchar;
	}
	void ParseToken( char* token ){
		SkipBadChars();
		char* endchar = FindBreakChar();
		strncpy( token, curTmp, endchar - curTmp );
		token[ endchar - curTmp ] = 0;
		curTmp = endchar;
	}
	int ParseInt(){
		SkipBadChars();
		if ( isdigit( *curTmp ) || *curTmp == '-' ){
			char* endchar = FindBreakChar();
			*endchar = 0;
			int ret = atoi( curTmp );
			curTmp = endchar+1;
			return ret;
		}else{
			MUTErrorTrace( L"not a integer!" );
			return 0;
		}
	}
	BOOL IsFloat(){
		SkipBadChars();
		return isdigit( *curTmp ) || *curTmp == '-' || *curTmp == '.';
	}
	float ParseFloat(){
		if ( IsFloat() ){
			char* endchar = FindBreakChar();
			*endchar = 0;
			float ret = atof( curTmp );
			curTmp = endchar+1;
			return ret;
		}else{
			MUTErrorTrace( L"not a float!" );
			return 0;
		}
	}
	void ParseQuoteString( char* str ){
		SkipBadChars();
		if ( *curTmp == '"' ){
			curTmp++;
			char* endquote = strchr( curTmp, '"' );
			strncpy( str, curTmp, endquote - curTmp );
			str[ endquote - curTmp ] = 0;
			curTmp = endquote+1;
		}else{
			MUTErrorTrace( L"not a quote string!" );
		}
	}
	void ParseBracketVec2( D3DXVECTOR2& v ){
		SkipBadChars();
		if ( *curTmp == '(' ){
			curTmp++;
			SkipBadChars();
			char* nextStart = FindBreakChar();
			*nextStart = 0;
			v.x = atof( curTmp );
			curTmp = nextStart + 1;
			SkipBadChars();

			nextStart = strchr( curTmp, ')' );
			*nextStart = 0;
			v.y = atof( curTmp );
			curTmp = nextStart + 1;
		}else{
			MUTErrorTrace( L"not a vec2!" );
		}
	}
	void ParseBracketVec3( D3DXVECTOR3& v ){
		SkipBadChars();
		if ( *curTmp == '(' ){
			curTmp++;
			SkipBadChars();
			char* nextStart = FindBreakChar();
			*nextStart = 0;
			v.x = atof( curTmp );
			curTmp = nextStart + 1;
			SkipBadChars();

			nextStart = FindBreakChar();
			*nextStart = 0;
			v.y = atof( curTmp );
			curTmp = nextStart + 1;
			SkipBadChars();

			nextStart = strchr( curTmp, ')' );
			*nextStart = 0;
			v.z = atof( curTmp );
			curTmp = nextStart + 1;
		}else{
			MUTErrorTrace( L"not a vec3!" );
		}
	}
	void ParseLine( char* str ){
		SkipBadChars();
		char* endchar = curTmp;
		while ( *endchar != 13 ) endchar++;
		while ( *endchar == 9 || *endchar == 13 || *endchar == 10 || *endchar == ' ' ) endchar--;
		endchar++;
		strncpy( str, curTmp, endchar - curTmp );
		str[ endchar - curTmp ] = 0;
		curTmp = endchar+1;
	}
	BOOL End(){
		SkipBadChars();
		if ( curTmp >= fileTmp+fsize || *curTmp == 0 )
			return TRUE;
		return FALSE;
	}
	void Close();
};

class CMUTMD5Stage{
public:
	TString blendtype;
	TString mapname;
	LPDIRECT3DTEXTURE9 pTex;
	BOOL bAlphaTest;
	float alphaTestRef;
	CMUTMD5Stage() : bAlphaTest(FALSE), alphaTestRef( 0 ), pTex(0) {}
	HRESULT CreateStage();
	void ParseStage( CMUTParser& parser );
	void Prepare( LPD3DXEFFECT pEffect );
};

class CMUTMD5Shader{
public:
	BOOL bCreated;
	TString diffusemap;
	LPDIRECT3DTEXTURE9 pDiffuseTex;
	TString bumpmap;
	LPDIRECT3DTEXTURE9 pBumpTex;
	TString specularmap;
	LPDIRECT3DTEXTURE9 pSpecularTex;
	vector< CMUTMD5Stage > stages;
	CMUTMD5Shader() : bCreated(FALSE), pDiffuseTex(0), pBumpTex(0), pSpecularTex(0) {}
	HRESULT CreateShader();
	void ParseShader( CMUTParser& parser );
	void Prepare( LPD3DXEFFECT pEffect );
};

class CMUTMD5Material{
public:
	map< TString, CMUTMD5Shader* > mtr;
	HRESULT LoadMaterialFromFile( LPWSTR fn );
	CMUTMD5Shader* GetShader( TString& shader );
};

class CMUTMD5Anim{
public:
	int numFrames;
	int numJoints;
	int frameRate;
	int numAnimatedComponents;
	CMUTMD5Joint* joints;
	int* flags;
	int* startIndices;
	float* frames;
	CMUTMD5Anim() : 
		numFrames(0), numJoints(0), frameRate(0), numAnimatedComponents(0), 
			joints(0), flags(0), startIndices(0), frames(0)
		{}
	HRESULT LoadAnimFromFile( LPWSTR fn );
	void ParseHierarchy( CMUTParser& parser );
	void ParseBounds( CMUTParser& parser );
	void ParseBaseframe( CMUTParser& parser );
	void ParseFrame( CMUTParser& parser );
	void Release();

	void GetFrame( int iframe, CMUTMD5Joint* js );
};


class CMUTMD5AnimManager{
public:
	map< TString, CMUTMD5Anim* > manim;
	HRESULT LoadAnimFromFile( LPWSTR fn );
	CMUTMD5Anim* GetAnim( TString& name );
	void Release();
};

class CMUTMD5Mesh{
public:
	CMUTMD5Shader* pShader;
	DWORD vnum;
	DWORD fnum;
	LPDIRECT3DVERTEXBUFFER9 vb;
	LPDIRECT3DINDEXBUFFER9 ib;
	int* firstWeight;
	int iNumWeight;
	int* numWeight;
	int* weightIndex;
	D3DXVECTOR4* scaledWeight;
	D3DXVECTOR3* normals;
	D3DXVECTOR3* tangents;
	CMUTMD5Mesh(): pShader(0), vb(0), ib(0), firstWeight(0), numWeight(0), weightIndex(0), scaledWeight(0){}
	HRESULT CreateVB();
	HRESULT CreateIB();
	void ParseMesh( CMUTParser& parser );
	void Release();
	void TransformVerts( D3DXMATRIX* jointmats );
	void ComputeNormal( D3DXMATRIX* invmats );
	void ComputeTangent( D3DXMATRIX* invmats );
	void Render( LPDIRECT3DDEVICE9 pDevice, LPD3DXEFFECT pEffect  );
};


class CMUTMD5Model{
public:
	CMUTMD5Anim* pAnim;
	int version;
	int numJoints, numMeshes;
	CMUTMD5Mesh* meshes;
	CMUTMD5Joint* joints;
	CMUTMD5Joint* jointsA;
	CMUTMD5Joint* jointsB;
	D3DXMATRIX* jointmats;
	CMUTMD5Model() : pAnim(0) {}
	HRESULT Create(LPWSTR fn);
	void ParseJoints( CMUTParser& parser, CMUTMD5Joint* curjoint, CMUTMD5Joint* joints );
	void TransformVerts();
	void ComputeNormal();
	void ComputeTangent();
	void UpdateFrame( float frame );
	void Render( LPD3DXEFFECT pEffect );
	void SetAnim( TString& name );
	void Release(){}
};

class CMUTCharacter{
public:
	map< TString, TString > manim;
	float frame;
	D3DXVECTOR3 pos;
	D3DXVECTOR3 dir;
	CMUTMD5Model* pModel;
	CMUTCharacter() : pModel(0), frame(0), pos(0,0,0), dir(1,0,0) {}
	BOOL BindModel( LPWSTR dir );
	void SetAnim( LPWSTR name );
	void SetAnim( int cnt );
	void UpdateFrame();
	void Render( LPD3DXEFFECT pEffect );
};